#include "StdAfx.h"
#include "AdderVariableScoper.h"

AdderVariableScoper::AdderVariableScoper(unsigned int scope_size)
{
	this->SCOPE_SIZE = scope_size;
	this->scope_level = 0;
	this->allocated_scopes = 0;

	// Now initalize the current scope.
	this->current_scope = (void**)calloc(this->SCOPE_SIZE,sizeof(void*));
	this->scopes[this->scope_level] = this->current_scope;
#ifdef SCOPE_ALLOC_DEBUG
	printf("SCOPER ALLOCATED LEVEL %u\n", this->scope_level);
#endif
	this->allocated_scopes += 1;
	// there is 1 allocated scopes, we're currently at scope 0.
}

AdderVariableScoper::~AdderVariableScoper(void)
{
	// Free everything :)
	for (int i = this->allocated_scopes - 1; i > -1; i--)
	{
		for (int n = 0; n < this->SCOPE_SIZE; n++)
		{
			// Erase each element
			this->current_scope = this->scopes[i];
			this->erase(n);
		}

		// Now free the memory allocated for this scope.
		free(this->scopes[i]);
	}
}

AdderValue* AdderVariableScoper::operator [](unsigned int n)
{
	// retrieve the AdderValue* at the index of the current scope.
	if (n >= this->SCOPE_SIZE) return NULL;
	if (this->current_scope[n] == NULL)
	{
		// allocate a new AdderValue* at this position
		AdderValue* t = new AdderValue();
		this->current_scope[n] = (void*)t;
#ifdef SCOPE_ALLOC_DEBUG
		printf("SCOPER ALLOCATED VALUE FOR SLOT %i ON LEVEL %u\n", n, this->scope_level);
#endif
		return (AdderValue*)this->current_scope[n];
	}
	else
		return (AdderValue*)this->current_scope[n];
}

void AdderVariableScoper::set(unsigned int n, AdderValue* t)
{
	// set an AdderValue* at a position
	if (n >= this->SCOPE_SIZE) return;
	this->current_scope[n] = t;
#ifdef SCOPE_ALLOC_DEBUG
	printf("SCOPER SET (REFERENCE) VALUE FOR SLOT %i ON LEVEL %u\n", n, this->scope_level);
#endif
}

unsigned int AdderVariableScoper::size()
{
	return this->SCOPE_SIZE;
}

unsigned int AdderVariableScoper::level()
{
	return this->scope_level;
}

bool AdderVariableScoper::exists(unsigned int n)
{
	if (n >= this->SCOPE_SIZE) return NULL;
	return (this->current_scope[n] != NULL);
}

void AdderVariableScoper::erase(unsigned int n)
{
	if (n >= this->SCOPE_SIZE) return;
	if (this->current_scope[n] != NULL)
	{
		delete (AdderValue*)this->current_scope[n];
		this->current_scope[n] = NULL;
#ifdef SCOPE_ALLOC_DEBUG
		printf("SCOPER DELETING VALUE FOR SLOT %i ON LEVEL %u\n", n, this->scope_level);
#endif
	}
}

void AdderVariableScoper::raise()
{
	this->_allocate_scope();
}

void AdderVariableScoper::clear()
{
	this->_clear_scope();
}

void AdderVariableScoper::lower()
{
	if (this->scope_level == 0) return;
	this->_deallocate_scope();
}

unsigned long AdderVariableScoper::hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

void AdderVariableScoper::_allocate_scope()
{
	// When we've been changing this->current_scope, it has also
	// been changing this->scopes[this->scope_level] since it's
	// a pointer (and points to the same memory).

	// Check to see if there's already a scope on the new level.
	if (this->scope_level >= this->allocated_scopes - 1)
	{
		// no scope, allocate one
		this->current_scope = (void**)calloc(this->SCOPE_SIZE,sizeof(void*));
		this->scopes[++this->scope_level] = this->current_scope;
		this->allocated_scopes += 1;
#ifdef SCOPE_ALLOC_DEBUG
		printf("SCOPER ALLOCATED LEVEL %u (TOTAL LEVELS ALLOCATED: %u)\n", this->scope_level, this->allocated_scopes);
#endif
	}
	else
	{
		// just raise the current level and set current_scope.
		this->current_scope = this->scopes[++this->scope_level];
#ifdef SCOPE_ALLOC_DEBUG
		printf("SCOPER REUSING LEVEL %u\n", this->scope_level);
#endif
	}
}

void AdderVariableScoper::_deallocate_scope()
{
	// Technically this doesn't deallocate the memory, rather it just
	// sets all of the registers to "UNSET" and then moves down the
	// scope level (keeping the number of allocated scopes the same).
	// This is faster than reallocating memory each time the scope
	// changes.
	assert(this->scope_level > 0);

	// First set all of the registers.
	this->_clear_scope();

	this->scope_level -= 1;
	this->current_scope = this->scopes[this->scope_level];
#ifdef SCOPE_ALLOC_DEBUG
	printf("SCOPER LOWERING LEVEL %u\n", this->scope_level);
#endif
}

void AdderVariableScoper::_clear_scope()
{
	// clears all the registers on the scope.
	for (int n = 0; n < this->SCOPE_SIZE; n += 1)
	{
		if (this->current_scope[n] != NULL)
			((AdderValue*)this->current_scope[n])->type = AdderValueType::UNSET;
	}
#ifdef SCOPE_ALLOC_DEBUG
	printf("SCOPER CLEARING LEVEL %u\n", this->scope_level);
#endif
}